Youtube videos:
* [https://www.youtube.com/watch?v=kr0mpwqttM0&t=34s]
* [https://www.youtube.com/watch?v=swU3c34d2NQ]
* [https://www.youtube.com/watch?v=FsAPt_9Bf3U&t=35s]
* [https://www.youtube.com/watch?v=KlBPCzcQNU8]
In [2]:
def square(x):
return x*x
def cube(x):
return x*x*x
# This is custom-built map function which is going to behave like in-bulit map function.
def my_map(func, arg_list):
result = []
for i in arg_list:
result.append(func(i))
return result
squares = my_map(square, [1,2,3,4])
print(squares)
cubes = my_map(cube, [1,2,3,4])
print(cubes)
In [3]:
def html_tag(tag):
def wrap_text(msg):
print('<{0}>{1}<{0}>'.format(tag, msg))
return wrap_text
print_h1 = html_tag('h1')
print_h1('Test Headline')
print_h1('Another Headline')
print_p = html_tag('p')
print_p('Test Paragraph!')
In [5]:
def decorator_function(original_function):
def wrapper_function():
print("wrapper executed this before {}".format(original_function.__name__))
return original_function()
return wrapper_function
def display():
print("display function ran!")
decorated_display = decorator_function(display)
decorated_display()
In [6]:
# The above code is functionally the same as below:
def decorator_function(original_function):
def wrapper_function():
print("wrapper executed this before {}".format(original_function.__name__))
return original_function()
return wrapper_function
@decorator_function
def display():
print("display function ran!")
display()
In [7]:
# Lets make our decorator function to work with functions with different number of arguments
# For this we use, *args (arguments) and **kwargs (keyword arguments).
# args and kwargs are convention, you can use any other name you want like *myargs, **yourkeywordargs
def decorator_function(original_function):
def wrapper_function(*args, **kwargs):
print("wrapper executed this before {}".format(original_function.__name__))
return original_function(*args, **kwargs)
return wrapper_function
@decorator_function
def display():
print("display function ran!")
@decorator_function
def display_info(name, age):
print('display_info ran with arguments ({}, {})'.format(name, age))
display()
display_info('John', 25)
In [8]:
# Now let's use a class as a decorator instead of a function
class decorator_class(object):
def __init__(self, original_function):
self.original_function = original_function
def __call__(self, *args, **kwargs): # This method is going to behave just like our wrapper function behaved
print('call method executed this before {}'.format(self.original_function.__name__))
return self.original_function(*args, **kwargs)
@decorator_class
def display():
print("display function ran!")
@decorator_class
def display_info(name, age):
print('display_info ran with arguments ({}, {})'.format(name, age))
display()
display_info('John', 25)
In [9]:
#Let's say we want to keep track of how many times a specific function was run and what argument were passed to that function
def my_logger(orig_func):
import logging
logging.basicConfig(filename='{}.log'.format(orig_func.__name__), level=logging.INFO) #Generates a log file in current directory with the name of original funcion
def wrapper(*args, **kwargs):
logging.info(
'Ran with args: {}, and kwargs: {}'.format(args, kwargs))
return orig_func(*args, **kwargs)
return wrapper
@my_logger
def display_info(name, age):
print('display_info ran with arguments ({}, {})'.format(name, age))
display_info('John', 25)
# Note:
# Since display_info is decorated with my_logger, the above call is equivalent to:
# decorated_display = my_logger(display_info)
# decorated_display('John', 25)
In [10]:
def my_timer(orig_func):
import time
def wrapper(*args, **kwargs):
t1 = time.time()
result = orig_func(*args, **kwargs)
t2 = time.time() - t1
print('{} ran in: {} sec'.format(orig_func.__name__, t2))
return result
return wrapper
@my_timer
def display_info(name, age):
print('display_info ran with arguments ({}, {})'.format(name, age))
display_info('John', 25)
# Note:
# Since display_info is decorated with my_timer, the above call is equivalent to:
# decorated_display = my_timer(display_info)
# decorated_display('John', 25)
# (or simply put)
# my_timer(display_info('John', 25))
In [11]:
@my_timer
@my_logger
def display_info(name, age):
print('display_info ran with arguments ({}, {})'.format(name, age))
display_info('John', 25) # This is equivalent to my_timer(my_logger(display_info('John', 25)))
# The above code will give us some unexpected results.
# Instead of printing "display_info ran in: ---- sec" it prints "wrapper ran in: ---- sec"
Let's see if switching the order of decorators helps
In [12]:
@my_logger
@my_timer
def display_info(name, age):
print('display_info ran with arguments ({}, {})'.format(name, age))
display_info('John', 25) # This is equivalent to my_logger(my_timer(display_info('John', 25)))
# Now this would create wrapper.log instead of display_info.log like we expected.
In [15]:
# To understand why wrapper.log is generated instead of display_info.log let's look at the following code.
def display_info(name, age):
print('display_info ran with arguments ({}, {})'.format(name, age))
display_info = my_timer(display_info('John',25))
print(display_info.__name__)
In [17]:
# So how do we solve this problem
# The answer is by using the wraps decorator
# For this we need to import wraps from functools module
from functools import wraps
def my_logger(orig_func):
import logging
logging.basicConfig(filename='{}.log'.format(orig_func.__name__), level=logging.INFO) #Generates a log file in current directory with the name of original funcion
@wraps(orig_func)
def wrapper(*args, **kwargs):
logging.info(
'Ran with args: {}, and kwargs: {}'.format(args, kwargs))
return orig_func(*args, **kwargs)
return wrapper
def my_timer(orig_func):
import time
@wraps(orig_func)
def wrapper(*args, **kwargs):
t1 = time.time()
result = orig_func(*args, **kwargs)
t2 = time.time() - t1
print('{} ran in: {} sec'.format(orig_func.__name__, t2))
return result
return wrapper
@my_logger
@my_timer
def display_info(name, age):
print('display_info ran with arguments ({}, {})'.format(name, age))
display_info('Hank', 22)
In [1]:
def prefix_decorator(prefix):
def decorator_function(original_function):
def wrapper_function(*args, **kwargs):
print(prefix, "Executed before {}".format(original_function.__name__))
result = original_function(*args, **kwargs)
print(prefix, "Executed after {}".format(original_function.__name__), '\n')
return result
return wrapper_function
return decorator_function
@prefix_decorator('LOG:')
def display_info(name, age):
print('display_info ran with arguments ({}, {})'.format(name, age))
display_info('John', 25)